import { ComponentFixture, fakeAsync, flush, TestBed, tick, waitForAsync } from '@angular/core/testing';
import { ChangeDetectionStrategy, Component, Injectable, OnInit } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { filter as _filter } from 'lodash';
import { delay } from 'rxjs/operators';
import { of } from 'rxjs';
import { OptionItem } from './interface';
import { SelectModule } from './select.module';

@Injectable()
export class DemoService {
  users: OptionItem[];

  constructor() {
    this.users = this.initUser();
  }

  // 模拟异步过滤返回数据
  getUsers(data: string = '') {
    const users = this.users.filter((item) => item.label.toLowerCase().includes(data.toLowerCase()));
    return of(users).pipe(delay(300));
  }

  initUser() {
    const users = Array.from({ length: 7 }).map((_, key) => ({
      label: `user${key + 100}`,
      age: 10 + key,
      isLeaf: true,
    }));
    return users;
  }
}

@Component({
  template: `
    <lv-select
      [lvOptions]="data"
      [(ngModel)]="selectedValue"
      [lvLoading]="loading"
      [lvPosition]="position"
      [lvInline]="inline"
      [lvSize]="size"
      [lvPlaceholder]="placeHolder"
      [lvDisabled]="disabled"
      [lvMode]="mode"
      [lvShowClear]="showClear"
      [lvShowFilter]="showFilter"
      [lvBeforeOpen]="beforeOpen"
      [lvShowCheckAll]="showCheckAll"
      [lvFilterMode]="filterMode"
      [lvFilterKey]="filterKey"
      [lvFilterCaseSensitive]="filterCaseSensitive"
      [lvAsyncFilter]="asyncFilter"
      [lvAsync]="async"
      [lvCustomFilter]="customFilter"
      [lvCustomItemFilter]="customItemFilter"
      [lvPanelClass]="panelClass"
      [lvValueKey]="valueKey"
      [lvCompareWith]="compareWith"
      (lvOpenChange)="openChange()"
      (lvClearChange)="clearChange()"
      (lvRemoveChange)="removeChange()"
      (lvFilterChange)="filterChange($event)"
    ></lv-select>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
class TestSelectComponent implements OnInit {
  data: OptionItem[];
  selectedValue;
  loading = false;
  size = 'default';
  placeHolder;
  disabled = false;
  mode = 'single';
  showClear = false;
  showFilter = false;
  showCheckAll = false;
  filterMode = 'startsWith';
  position = 'bottomLeft';
  inline = false;
  filterCaseSensitive = false;
  filterKey = 'label';
  asyncFilter = false;
  async = false;
  valueKey;
  compareWith;
  panelClass;
  beforeOpen;
  customFilter;
  customItemFilter;

  constructor(private demoService: DemoService) {}

  openChange() {}

  clearChange() {}

  removeChange() {}

  filterChange(value: string) {
    if (this.asyncFilter) {
      this.getUsers(value);
    }
  }

  getUsers(data?: string) {
    this.demoService.getUsers(data).subscribe((res) => {
      this.data = res;
    });
  }

  ngOnInit(): void {
    if (this.asyncFilter) {
      this.getUsers();
    } else {
      this.initData();
    }
  }

  initData() {
    this.data = Array.from({ length: 5 }).map((_, key) => this.initUser(key));
  }

  initUser(key: number) {
    return {
      age: `Age${key + 10}`,
      label: `user${key}`,
      name: { age: `Age${key + 10}` },
      isLeaf: true,
    };
  }
}

describe('SelectComponent', () => {
  beforeEach(waitForAsync(() => {
    TestBed.configureTestingModule({
      declarations: [TestSelectComponent],
      imports: [SelectModule, FormsModule, NoopAnimationsModule],
      providers: [DemoService],
      teardown: { destroyAfterEach: true },
    }).compileComponents();
  }));

  describe('render', () => {
    let component: TestSelectComponent;
    let fixture: ComponentFixture<TestSelectComponent>;
    const renderComp = fakeAsync((para) => {
      for (const [key, value] of Object.entries(para)) {
        component[key] = value;
      }
      fixture.detectChanges();
      tick(1500);
      fixture.detectChanges();
      expect(fixture).toMatchSnapshot();
      tick(1500);
    });

    beforeEach(() => {
      fixture = TestBed.createComponent(TestSelectComponent);
      component = fixture.componentInstance;
    });

    it('with lvLoading', () => {
      renderComp({ loading: true });
    });

    it('with lvSize small', () => {
      renderComp({ size: 'small' });
    });

    it('with lvSize large', () => {
      renderComp({ size: 'large' });
    });

    it('with lvPlaceholder', () => {
      renderComp({ placeHolder: 'select' });
    });

    it('with lvDisabled', () => {
      renderComp({ disabled: true });
    });

    // lvPosition不同触发点渲染
    it('with lvPosition', fakeAsync(() => {
      ['topLeft', 'topCenter', 'topRight', 'bottomCenter', 'bottomRight'].forEach((position) => {
        component.position = position;
        fixture.detectChanges();
        tick(1500);
        fixture.detectChanges();
        const select = fixture.nativeElement.querySelector('lv-select');
        select.dispatchEvent(new Event('click'));
        tick(1500);
        fixture.detectChanges();
        expect(document.activeElement).toMatchSnapshot();
      });
    }));

    // 内联模式渲染
    it('with lvInline', () => {
      renderComp({ inline: true });
    });
  });

  describe('work', () => {
    let component: TestSelectComponent;
    let fixture: ComponentFixture<TestSelectComponent>;

    // 点击弹出选择面板
    const clickPanel = () => {
      const select = fixture.nativeElement.querySelector('lv-select');
      select.dispatchEvent(new Event('click'));
      tick(1500);
      fixture.detectChanges();
    };

    beforeEach(fakeAsync(() => {
      fixture = TestBed.createComponent(TestSelectComponent);
      component = fixture.componentInstance;
      fixture.detectChanges();
      tick(500);
    }));

    // lvBeforeOpen下拉面板打开前回调触发，传入false下拉面板无法打开
    it('should work with lvBeforeOpen', fakeAsync(() => {
      component.beforeOpen = () => false;
      component.ngOnInit();
      fixture.detectChanges();
      clickPanel();
      expect(document.querySelector('.lv-select-panel')).toBeNull();
    }));

    // 单选能正常选择，点击展开后触发lvOpenChange回调和ngModelChange回调；删除正常触发lvClearChange回调
    it('should work with single mode', fakeAsync(() => {
      jest.spyOn(component, 'clearChange');
      jest.spyOn(component, 'openChange');
      component.showClear = true;
      component.ngOnInit();
      fixture.detectChanges();

      // 点击展开下拉面板
      clickPanel();
      expect(component.openChange).toBeCalled();

      // 选择第一个选项选中
      const option = document.querySelector('lv-option');
      option.dispatchEvent(new Event('click'));
      tick(1500);
      fixture.detectChanges();
      const selectValue = fixture.nativeElement.querySelector('.lv-select-value');
      expect(selectValue.textContent).toContain('user0');

      // 选择删除按钮删除
      const clearIcon = fixture.nativeElement.querySelector('.lv-select-clear .lv-icon-host');
      clearIcon.dispatchEvent(new Event('click'));
      tick(1500);
      fixture.detectChanges();
      const selectValue2 = fixture.nativeElement.querySelector('.lv-select-value');
      expect(selectValue2.textContent).not.toContain('user0');
      expect(component.clearChange).toBeCalled();
    }));

    // 配置多选后，能正常多选，ngModelChange回调；选中后能单个删除，触发lvRemoveChange回调
    it('should work with multiple mode', fakeAsync(async () => {
      jest.spyOn(component, 'removeChange');
      component.mode = 'multiple';
      component.ngOnInit();
      fixture.detectChanges();

      // 选择多个选项
      clickPanel();
      const options = document.querySelectorAll('lv-option');
      options[0].dispatchEvent(new Event('click'));
      options[1].dispatchEvent(new Event('click'));
      options[2].dispatchEvent(new Event('click'));
      tick(1500);
      fixture.detectChanges();

      // 选择一个删除
      const selectValue = fixture.nativeElement.querySelector('.lv-select-value');
      expect(selectValue.textContent).toContain('user0');
      const removeIcon = fixture.nativeElement.querySelector('.lv-icon-host');
      removeIcon.dispatchEvent(new Event('click'));
      tick(1500);
      fixture.detectChanges();
      expect(selectValue.textContent).not.toContain('user0');
      expect(component.removeChange).toBeCalled();

      // TODO:待完成，功能验证ok，但是用例执行受到阻塞，后续添加
    }));

    // 配置全选后，点击全选能全部选中
    it('should work when lvShowCheckAll is true ', fakeAsync(() => {
      component.showCheckAll = true;
      component.mode = 'multiple';
      component.ngOnInit();
      fixture.detectChanges();
      clickPanel();

      const all = document.querySelector('.lv-option-multiple');
      all.dispatchEvent(new Event('click'));
      tick(1500);
      fixture.detectChanges();
      const selectValue = fixture.nativeElement.querySelector('.lv-select-value');
      expect(selectValue.textContent).toContain('user0');
      tick(1500);
    }));

    // 输入后能正常过滤（触发lvFilterChange回调）
    it('should work when lvShowFilter is true', fakeAsync(() => {
      jest.spyOn(component, 'filterChange');
      component.showFilter = true;
      component.filterMode = 'contains';
      component.ngOnInit();
      fixture.detectChanges();
      clickPanel();

      const options = document.querySelectorAll('lv-option');
      expect(options.length).toEqual(5);
      const input = document.querySelector('.lv-input') as HTMLInputElement;
      input.value = '1';
      input.dispatchEvent(new Event('input'));
      tick(1500);
      fixture.detectChanges();
      const options2 = document.querySelectorAll('lv-option');
      // 搜索过滤后只有1个匹配的选项
      expect(options2.length).toEqual(1);
      expect(component.filterChange).toBeCalled();
      flush();
    }));

    // 不同lvFilterKey搜索的主键，lvFilterCaseSensitive区分大小写
    it('should work with lvFilterKey and lvFilterCaseSensitive', fakeAsync(() => {
      component.showFilter = true;
      component.filterKey = 'age';
      component.filterCaseSensitive = true;
      component.ngOnInit();
      fixture.detectChanges();
      clickPanel();

      const input = document.querySelector('.lv-input') as HTMLInputElement;
      input.value = 'Age10';
      input.dispatchEvent(new Event('input'));
      tick(1500);
      fixture.detectChanges();
      const options = document.querySelectorAll('lv-option');
      expect(options.length).toEqual(1);
      expect(options[0].textContent).toEqual('user0');

      // 大小写敏感，age10无法过滤成功
      input.value = 'age10';
      input.dispatchEvent(new Event('input'));
      tick(1500);
      fixture.detectChanges();
      const options2 = document.querySelectorAll('lv-option');
      expect(options2.length).toEqual(0);
      flush();
    }));

    // lvAsync和lvAsyncFilter 搜索异步过滤生效
    it('should work when lvAsyncFilter is true', fakeAsync(() => {
      component.showFilter = true;
      component.asyncFilter = true;
      component.async = true;
      component.ngOnInit();
      fixture.detectChanges();
      clickPanel();

      const input = document.querySelector('.lv-input') as HTMLInputElement;
      input.value = 'user100';
      input.dispatchEvent(new Event('input'));
      tick(1500);
      fixture.detectChanges();
      tick(1500);
      fixture.detectChanges();
      tick(1500);
      const options = document.querySelectorAll('lv-option');
      expect(options.length).toEqual(1);
      expect(options[0].textContent).toEqual('user100');
    }));

    // 全部数据自定义过滤生效 lvCustomFilter
    it('should work with lvCustomFilter', fakeAsync(() => {
      component.showFilter = true;
      component.customFilter = (data, _) => _filter(data, (item) => item.age === 'Age11');
      component.ngOnInit();
      fixture.detectChanges();
      clickPanel();

      const input = document.querySelector('.lv-input') as HTMLInputElement;
      input.value = 'user0';
      input.dispatchEvent(new Event('input'));
      tick(1500);
      fixture.detectChanges();
      const options = document.querySelectorAll('lv-option');
      expect(options.length).toEqual(1);
      expect(options[0].textContent).toEqual('user1');
      flush();
    }));

    // 单项数据自定义过滤生效 lvCustomItemFilter
    it('should work with lvCustomItemFilter', fakeAsync(() => {
      component.showFilter = true;
      component.customItemFilter = () => true;
      component.ngOnInit();
      fixture.detectChanges();
      clickPanel();

      const input = document.querySelector('.lv-input') as HTMLInputElement;
      input.value = 'user0';
      input.dispatchEvent(new Event('input'));
      tick(1500);
      fixture.detectChanges();
      const options = document.querySelectorAll('lv-option');
      expect(options.length).toEqual(5);
      flush();
    }));

    // lvPanelClass传入生效
    it('should work with lvPanelClass', fakeAsync(() => {
      component.panelClass = 'test-panel-class';
      component.ngOnInit();
      fixture.detectChanges();
      clickPanel();
      tick(1500);

      expect(document.querySelector('.test-panel-class')).not.toBeNull();
    }));

    // 选中比较方式生效，lvValueKey和lvCompareWith同时传入（lvCompareWith返回值不在optionItem中）
    it('should work with lvValueKey and lvCompareWith', fakeAsync(() => {
      component.mode = 'multiple';
      component.valueKey = 'name';
      component.compareWith = 'age';
      component.ngOnInit();
      fixture.detectChanges();
      clickPanel();

      const options = document.querySelectorAll('lv-option');
      options[0].dispatchEvent(new Event('click'));
      tick(1500);
      fixture.detectChanges();
      tick(1500);

      const selectValue = fixture.nativeElement.querySelector('.lv-select-value');
      expect(selectValue.textContent).toContain('user0');
    }));

    // TODO 虚拟滚动场景测试lvVirtualScroll,lvVirtualItemSize,lvVirtualMaxBuffer（当前UT环境不支持）
  });
});
